package com.ls.fw.data.search.impl.lucene.parser;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.NumericRangeQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TermRangeQuery;
import org.apache.lucene.search.WildcardQuery;

import com.ls.fw.common.beanparser.enums.ConnectType;
import com.ls.fw.common.beanparser.enums.OperatorType;
import com.ls.fw.common.beanparser.schema.Expression;
import com.ls.fw.common.beanparser.visitor.ExpressionVisitor;
import com.ls.fw.data.search.impl.lucene.bean.ValueType;
import com.ls.fw.data.search.impl.lucene.exception.IndexException;
import com.ls.fw.data.search.impl.lucene.query.builder.BooleanQueryBuilder;
import com.ls.fw.data.search.impl.lucene.util.IndexUtil;



public class SubExpressionParser implements ExpressionVisitor{

	private StringBuilder sql = new StringBuilder();
	private Map<String,Object> value = new HashMap<String,Object>();
	private BooleanQueryBuilder query = BooleanQueryBuilder.builder();
	private ConnectType connectType = null;
	public SubExpressionParser(List<Expression> list,ConnectType connectType){
		this(list,connectType, true);
	}
	
	public SubExpressionParser(Expression expression){
		this.exeExp(expression,this);
	}
	
	public SubExpressionParser(List<Expression> list){
		this(list, true);
	}
	public SubExpressionParser(List<Expression> list,boolean addFlag){
		this(list,ConnectType.AND, addFlag);
	}
		
	public SubExpressionParser(List<Expression> list,ConnectType connectType,boolean addFlag){
		this.connectType = connectType;
		if(addFlag){
			this.sql.append(" "+connectType.getName()+" (1=1");
		}
		if(list!=null){
			for (Expression e : list) {
				this.exeExp(e,this);
			}
		}
		//this.sql.append(sql);
		if(addFlag){
			this.sql.append(")");
		}
		//System.out.println("最终sql:"+this.sql);
	}
 
	/**
	 * 执行表达式
	 * @param expression
	 */
	private void exeExp(Expression expression,SubExpressionParser p){
		//System.out.println("expression:"+expression);
		if(expression!=null){
			if(expression.hasSubExp()){
				//System.out.println("子查询:开始");
				SubExpressionParser sp = new SubExpressionParser(expression.getSubExps(),expression.getWhereType());
				 if(this.isAnd(expression.getWhereType())){
					 query.must(sp.getQuery());
				 }else{
					 query.should(sp.getQuery());
				 }
				this.sql.append(sp.getSql());
				//System.out.println("sql："+sql);
				//System.out.println("子查询:结束\n ");
			}else{
				//System.out.println("非子查询:");
				expression.accept(p);
			}
		}
	}
	
	public void visitsql(Expression expression) {
		String paramName = expression.getParamName();
		if (paramName==null || "".equals(paramName)) {
			paramName = expression.getName();
		}
		if(expression.getTable()==null){
			return ;
		}
		
		String name = expression.getTable().getName();
		String alias = expression.getTable().getAlias();
		if(StringUtils.isBlank(alias)){
			alias = name;
		}
		OperatorType  operatorType = expression.getOperator();
		if(operatorType==null || StringUtils.isBlank(alias)){
			return ;
		}
		this.value.put(paramName, expression.getValue());
		switch(operatorType){
			case LIKE:
				 if(this.isAnd(expression.getWhereType())){
					sql.append(" AND "+alias+ "."+expression.getName() + " like :"+paramName );
			     }else{
			    	sql.append(" OR "+ alias+ "."+expression.getName() + " like :"+paramName );
				 }
				break;
			case PRE_LIKE:
				 if(this.isAnd(expression.getWhereType())){
					sql.append(" AND "+alias+ "."+expression.getName() + " like :"+paramName );
			     }else{
			    	sql.append(" OR "+ alias+ "."+expression.getName() + " like :"+paramName );
				 }
				break;
			case LAST_LIKE:
				 if(this.isAnd(expression.getWhereType())){
					sql.append(" AND "+alias+ "."+expression.getName() + " like :"+paramName );
			     }else{
			    	sql.append(" OR "+ alias+ "."+expression.getName() + " like :"+paramName );
				 }
				break;
			case GREATER:
				if(this.isAnd(expression.getWhereType())){
					sql.append(" AND "+ alias+ "."+expression.getName() + " > :"+paramName );
				}else{
					sql.append(" OR "+ alias+ "."+expression.getName() + " > :"+paramName );
			    }
				break;
			case GREATER_EQUAL:
				if(this.isAnd(expression.getWhereType())){
					sql.append(" AND "+ alias+ "."+expression.getName() + " >=:"+paramName );
				}else{
					sql.append(" OR "+ alias+ "."+expression.getName() + " >=:"+paramName );
			    }
				break;
			case LESS:
				if(this.isAnd(expression.getWhereType())){
					sql.append(" AND "+ alias+ "."+expression.getName() + " <:"+paramName );
				}else{
					sql.append(" OR "+ alias+ "."+expression.getName() + " <:"+paramName );
			    }
				break;
			case LESS_EQUAL:
				if(this.isAnd(expression.getWhereType())){
					sql.append(" AND "+ alias+ "."+expression.getName() + " <=:"+paramName );
				}else{
					sql.append(" OR "+ alias+ "."+expression.getName() + " <=:"+paramName );
			    }
				break;
			case BETWEEN:
				if(this.isAnd(expression.getWhereType())){
					sql.append(" AND "+ alias+ "."+expression.getName() + " BETWEEN :"+paramName +" AND :" +paramName+"1");
				}else{
					sql.append(" OR "+ alias+ "."+expression.getName() + " BETWEEN :"+paramName +" AND :" +paramName+"1");
			    }
				this.value.put(paramName+"1", expression.getValue1());
				break;
			case NOT_BETWEEN:
				if(this.isAnd(expression.getWhereType())){
					sql.append(" AND "+ alias+ "."+expression.getName() + " NOT BETWEEN :"+paramName +" AND :" +paramName+"1");
				}else{
					sql.append(" OR "+ alias+ "."+expression.getName() + " NOT BETWEEN :"+paramName +" AND :" +paramName+"1");
			    }
				this.value.put(paramName+"1", expression.getValue1());
				break;
			case IN:
				if(expression.getValue()!=null&& !"".equals(expression.getValue())){
					if(this.isAnd(expression.getWhereType())){
						sql.append(" AND "+ alias+ "."+expression.getName() + " IN(:"+paramName+")");
					}else{
						sql.append(" OR "+ alias+ "."+expression.getName() + " IN(:"+paramName+")");
				    }
				}
				break;
			case NOT_IN:
				if(expression.getValue()!=null&& !"".equals(expression.getValue())){
					if(this.isAnd(expression.getWhereType())){
						sql.append(" AND "+ alias+ "."+expression.getName() + " NOT IN(:"+paramName+")");
					}else{
						sql.append(" OR "+ alias+ "."+expression.getName() + " NOT IN(:"+paramName+")");
				    }
				}
				break;	
			case IS_NULL:
				if(this.isAnd(expression.getWhereType())){
					sql.append(" AND "+ alias+ "."+expression.getName()+" IS NULL ");
				}else{
					sql.append(" OR "+ alias+ "."+expression.getName()+" IS NULL ");
			    }
				break;
			case IS_NOT_NULL:	
				if(this.isAnd(expression.getWhereType())){
					sql.append(" AND  "+ alias+ "."+expression.getName()+" IS NOT NULL ");
				}else{
					sql.append(" OR  "+ alias+ "."+expression.getName()+" IS NOT NULL ");
			    }
				break;
			case IS_EMPTY:	
				if(this.isAnd(expression.getWhereType())){
					sql.append(" AND ("+ alias+ "."+expression.getName()+" IS NULL "+" OR  " + alias+ "."+expression.getName() + " = '')");
				}else{
					sql.append(" OR ("+ alias+ "."+expression.getName()+" IS NULL "+" OR  " + alias+ "."+expression.getName() + " = '')");
			    }
				break;
			case IS_NOT_EMPTY:	
				if(this.isAnd(expression.getWhereType())){
					sql.append(" AND  ("+ alias+ "."+expression.getName()+" IS NOT NULL "+" and  " + alias+ "."+expression.getName() + " != '')");
				}else{
					sql.append(" OR  ("+ alias+ "."+expression.getName()+" IS NOT NULL "+" and  " + alias+ "."+expression.getName() + " != '')");
			    }
				break;
			case NOT_EQUAL:	
				if(this.isAnd(expression.getWhereType())){
					sql.append(" AND "+ alias+ "."+expression.getName() + " !=:"+paramName );
				}else{
					sql.append(" OR "+ alias+ "."+expression.getName() + " !=:"+paramName );
			    }
				break;
			default:
				if(this.isAnd(expression.getWhereType())){
					sql.append(" AND "+ alias+ "."+expression.getName() + " =:"+paramName );
				}else{
					sql.append(" OR "+ alias+ "."+expression.getName() + " =:"+paramName );
			    }
				break;
		}
	}

	@Override
	public void visit(Expression expression) {
		//测试
		this.visitsql(expression);
		
		
		String paramName = expression.getParamName();
		if (paramName==null || "".equals(paramName)) {
			paramName = expression.getName();
		}
		OperatorType  operatorType = expression.getOperator();
		if(operatorType==null){
			return ;
		}
		Query condition = null;
		switch(operatorType){
			case LIKE:
				 condition = new WildcardQuery(new Term(expression.getName(), WildcardQuery.WILDCARD_STRING+String.valueOf(expression.getValue())+ WildcardQuery.WILDCARD_STRING));
				 break;
			case NOT_LIKE:
				 condition = new WildcardQuery(new Term(expression.getName(), WildcardQuery.WILDCARD_STRING+String.valueOf(expression.getValue())+ WildcardQuery.WILDCARD_STRING));
				 condition = BooleanQueryBuilder.not(condition);
				 break;
			case PRE_LIKE:
				condition = new WildcardQuery(new Term(expression.getName(), WildcardQuery.WILDCARD_STRING+String.valueOf(expression.getValue())));
				break;
			case LAST_LIKE:
				condition = new WildcardQuery(new Term(expression.getName(), String.valueOf(expression.getValue())+WildcardQuery.WILDCARD_STRING));
				break;
			case NOT_CHAR:
				 condition = new WildcardQuery(new Term(expression.getName(), WildcardQuery.WILDCARD_CHAR+String.valueOf(expression.getValue())+ WildcardQuery.WILDCARD_CHAR));
				 condition = BooleanQueryBuilder.not(condition);
				 break;
			case CHAR:
				 condition = new WildcardQuery(new Term(expression.getName(), WildcardQuery.WILDCARD_CHAR+String.valueOf(expression.getValue())+ WildcardQuery.WILDCARD_CHAR));
				 break;
			case PRE_CHAR:
				condition = new WildcardQuery(new Term(expression.getName(), WildcardQuery.WILDCARD_CHAR+String.valueOf(expression.getValue())));
				break;
			case LAST_CHAR:
				condition = new WildcardQuery(new Term(expression.getName(), String.valueOf(expression.getValue())+WildcardQuery.WILDCARD_CHAR));
				break;
			case GREATER:
				condition = this.getBetweenQuery(expression.getName(), expression.getValue()
						, expression.getValue1(),OperatorType.GREATER);
				break;
			case GREATER_EQUAL:
				condition = this.getBetweenQuery(expression.getName(), expression.getValue()
						, expression.getValue1(),OperatorType.GREATER_EQUAL);
				break;
			case LESS:
				condition = this.getBetweenQuery(expression.getName(), expression.getValue1()
						, expression.getValue(),OperatorType.LESS);
				break;
			case LESS_EQUAL:
				condition = this.getBetweenQuery(expression.getName(), expression.getValue1()
						, expression.getValue(),OperatorType.LESS_EQUAL);
				break;
			case BETWEEN:
				condition = this.getBetweenQuery(expression.getName(), expression.getValue()
						, expression.getValue1(),OperatorType.BETWEEN);
				break;
			case NOT_BETWEEN:
				condition = this.getBetweenQuery(expression.getName(), expression.getValue()
						, expression.getValue1(),OperatorType.BETWEEN);
				condition = BooleanQueryBuilder.not(condition);
				break;
			case IN:
				 Object v = expression.getValue();
				 if(v!=null){
					 String value = String.valueOf(v);
					 String[] vals = value.split(",");
					 BooleanQueryBuilder in = new  BooleanQueryBuilder();
					 ValueType t = null;
					 if(expression.getValueType()!=null){
						t = IndexUtil.getValueType(expression.getValueType());
					 }
					 for (String string : vals) {
						 condition = this.getEqualQuery(expression.getName(), parser(string,t));
						 in.should(condition);
						 condition = null;
					}
					condition = in.get();
				 }
				break;
			case NOT_IN:
				 v = expression.getValue();
				 if(v!=null){
					 String value = String.valueOf(v);
					 String[] vals = value.split(",");
					 BooleanQueryBuilder in = new  BooleanQueryBuilder();
					 ValueType t = null;
					 if(expression.getValueType()!=null){
						t = IndexUtil.getValueType(expression.getValueType());
					 }
					 for (String string : vals) {
						 condition = this.getEqualQuery(expression.getName(), parser(string,t));
						 in.must(BooleanQueryBuilder.not(condition));
						 condition = null;
					}
					condition = in.get();
				 }
				break;	
			case IS_NULL:
				condition = this.getBetweenQuery(expression.getName(), "''"
						, WildcardQuery.WILDCARD_STRING,OperatorType.IS_NULL);
				condition = BooleanQueryBuilder.not(condition);
				break;
			case IS_NOT_NULL:	
				condition = this.getBetweenQuery(expression.getName(), "''"
						, WildcardQuery.WILDCARD_STRING,OperatorType.IS_NOT_NULL);
				break;
			case IS_EMPTY:	
				condition = this.getEqualQuery(expression.getName(), "''" );
				break;
			case IS_NOT_EMPTY:	
				condition = this.getEqualQuery(expression.getName(), "''" );
				condition = BooleanQueryBuilder.not(condition);
				break;
			case NOT_EQUAL:	
				condition = this.getEqualQuery(expression.getName(), expression.getValue());
				condition = BooleanQueryBuilder.not(condition);
				break;
			default:
				condition = this.getEqualQuery(expression.getName(), expression.getValue());
				break;
		}
		if(condition!=null){
			 condition.setBoost(expression.getBoost());
			 if(this.isAnd(expression.getWhereType())){
				 query.must(condition);
			 }else{
				 query.should(condition);
				 //query.should(BooleanQueryBuilder.or(condition));
			 }
		}
	}
	
	private Object parser(String val,ValueType t){
		if(t==null){
			return val;
		}
		Object value1 = val;
		 switch(t){
			case DOUBLE:
				value1 = Double.parseDouble(val);
				break;
			case FLOAT:
				value1 = Float.parseFloat(val);
				break;
			case LONG:
				value1 = Long.parseLong(val);
				break;
			case INT:
				value1 = Integer.parseInt(val);
				break;
			default:
				break;
			}
		 return value1;
	}

	private Query getBetweenQuery(String name,Object m_value
			,Object m_value1,OperatorType  operatorType){
		Object value = m_value;
		Object value1 = m_value1;
		ValueType t = IndexUtil.getValueType(value);
		ValueType t1 = IndexUtil.getValueType(value1);
		t = ValueType.getValueType(Math.max(t.getBit(),t1.getBit()));
		boolean min_e = true,max_e = true;
		switch(operatorType){
			case BETWEEN:
				break;
			case NOT_BETWEEN:
				break;
			case IS_NULL:
				break;
			case IS_NOT_NULL:
				break;
			case GREATER:
				{
					min_e = false;
					switch(t){
					case DOUBLE:
						value1 = Double.MAX_VALUE;
						break;
					case FLOAT:
						value1 = Float.MAX_VALUE;
						break;
					case LONG:
						value1 = Long.MAX_VALUE;
						break;
					case INT:
						value1 = Integer.MAX_VALUE;
						break;
					case STRING:
						value1 = WildcardQuery.WILDCARD_STRING;
						break;
					default:
						value1 = Long.MAX_VALUE;
						break;
					}
				}
				break;
			case GREATER_EQUAL:
				{
					switch(t){
					case DOUBLE:
						value1 = Double.MAX_VALUE;
						break;
					case FLOAT:
						value1 = Float.MAX_VALUE;
						break;
					case LONG:
						value1 = Long.MAX_VALUE;
						break;
					case INT:
						value1 = Integer.MAX_VALUE;
						break;
					case STRING:
						value1 = WildcardQuery.WILDCARD_STRING;
						break;
					default:
						value1 = Long.MAX_VALUE;
						break;
					}
				}
				break;
			case LESS:
				{
					max_e = false;
					switch(t){
					case DOUBLE:
						value = Double.MIN_VALUE;
						break;
					case FLOAT:
						value = Float.MIN_VALUE;
						break;
					case LONG:
						value = Long.MIN_VALUE;
						break;
					case INT:
						value = Integer.MIN_VALUE;
						break;
					case STRING:
						value = WildcardQuery.WILDCARD_STRING;
						break;
					default:
						value = 0;
						break;
					}
				}
				break;
			case LESS_EQUAL:
				{
					switch(t){
					case DOUBLE:
						value1 = Double.MIN_VALUE;
						break;
					case FLOAT:
						value1 = Float.MIN_VALUE;
						break;
					case LONG:
						value1 = Long.MIN_VALUE;
						break;
					case INT:
						value1 = Integer.MIN_VALUE;
						break;
					case STRING:
						value1 = WildcardQuery.WILDCARD_STRING;
						break;
					default:
						value1 = 0;
						break;
					}
				}
				break;
			default:
				break;
		}
		Query query = null;
		switch(t){
			case NONE:
				throw new IndexException("value不能为空！");
			case DOUBLE:
				query = NumericRangeQuery.newDoubleRange(name, Double.valueOf(String.valueOf(value)), Double.valueOf(String.valueOf(value1)), min_e, max_e);
				break;
			case FLOAT:
				query = NumericRangeQuery.newFloatRange(name, Float.valueOf(String.valueOf(value)), Float.valueOf(String.valueOf(value1)), min_e, max_e);
				break;
			case LONG:
				query = NumericRangeQuery.newLongRange(name, Long.valueOf(String.valueOf(value)), Long.valueOf(String.valueOf(value1)), min_e, max_e);
				break;
			case INT:
				query = NumericRangeQuery.newIntRange(name, Integer.valueOf(String.valueOf(value)), Integer.valueOf(String.valueOf(value1)), min_e, max_e);
				break;
			case STRING:
				query = TermRangeQuery.newStringRange(name, String.valueOf(value), String.valueOf(value1),min_e, max_e);
				break;
			default:
				throw new IndexException(name+"的类型为"+t+"，不支持范围查询！");
		}
		return query;
	}
	
	private Query getEqualQuery(String name,Object value){
		ValueType t = IndexUtil.getValueType(value);
		Query query = null;
		switch(t){
			case NONE:
				throw new IndexException("value不能为空！");
			case DOUBLE:
				query = NumericRangeQuery.newDoubleRange(name, Double.valueOf(String.valueOf(value)), Double.valueOf(String.valueOf(value)), true, true);
				break;
			case FLOAT:
				query = NumericRangeQuery.newFloatRange(name, Float.valueOf(String.valueOf(value)), Float.valueOf(String.valueOf(value)), true, true);
				break;
			case LONG:
				query = NumericRangeQuery.newLongRange(name, Long.valueOf(String.valueOf(value)), Long.valueOf(String.valueOf(value)), true, true);
				break;
			case INT:
				query = NumericRangeQuery.newIntRange(name, Integer.valueOf(String.valueOf(value)), Integer.valueOf(String.valueOf(value)), true, true);
				break;
			case STRING:
				query = new TermQuery(new Term(name, String.valueOf(value)));
				break;
		}
		return query;
	}

	/**
	 * 是否为and
	 * @author Defender
	 * 2014年2月11日 下午10:44:57
	 * @param connectType
	 * @return
	 */
	private boolean isAnd(ConnectType connectType){
		return ConnectType.AND.equals(connectType);
	}
	
	public StringBuilder getSql() {
		return sql;
	}

	public void setSql(StringBuilder sql) {
		this.sql = sql;
	}

	public void setValue(Map<String,Object> value) {
		this.value = value;
	}

	public Map<String,Object> getValue() {
		return value;
	}

	public BooleanQuery getQuery() {
		return query.get();
	}

	public ConnectType getConnectType() {
		return connectType;
	}

	public void setConnectType(ConnectType connectType) {
		this.connectType = connectType;
	}
}
